package com.davidlife.jvm.gc.softrefqueue;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;

//Run this class using JVM args: -Xmx10m
public class SoftRefQueue {
    public static class User {
        public int id;
        public String name;

        public User(int id, String name) {
            this.id = id;
            this.name = name;
        }

        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    '}';
        }
    }

    /**
     * 这里实现一个自定义的软引用类，扩展软引用的目的是记录User.uid，后续再引用队列中，就可以通过这个uid字段知道那个User实例被回收了。
     */
    public static class UserSoftReference extends SoftReference<User> {
        int uid;
        public UserSoftReference(User user, ReferenceQueue<? super User> queue) {
            super(user, queue);
            this.uid = user.id;
        }
    }

    static ReferenceQueue<User> softRefQueue = null;

    public static class CheckRefQueue extends Thread {
        @Override
        public void run() {
            while (true) {
                if (softRefQueue != null) {
                    UserSoftReference obj = null;
                    try {
                        obj = (UserSoftReference)softRefQueue.remove();  //阻塞方法
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    /*
                    引用博客中一句话：
                        当大量使用Reference时，虽然Reference指向的对象可能被回收了，但Reference本身也是个对象，
                        所以也需要回收。这时就需要使用ReferenceQueue了。
                     */

                    //Reference(SoftReference，WeakReference，PhantomReference的父类)对象进入ReferenceQueue，说明它的实际对象User被回收了。
                    if (obj != null) {
                        System.out.println("user id " + obj.uid + " is delete(has been GC) !");
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new CheckRefQueue();
        thread.setDaemon(true);
        thread.start();

        User user = new User(1, "Tom");
        softRefQueue = new ReferenceQueue<>();

        //在创建软引用时，指定了一个软引用队列，当给定的对象实例被回收时，相应的Reference对象（这里为UserSoftReference对象）就会被加入到这个引用队列
        //通过访问（检查）该队列可以跟踪对象的回收情况。
        UserSoftReference userSoftRef = new UserSoftReference(user, softRefQueue);

        user = null;  //去除强引用
        System.out.println("Before GC");
        System.out.println(userSoftRef.get());

        System.gc();
        System.out.println("After First GC");
        System.out.println(userSoftRef.get());

        //（创建大数组，耗尽内存）
        byte[] bytes = new byte[1024 * 938 * 7];
        System.gc(); //这里其实不用再调用System.gc()，因为在分配大数据时，系统会自动进行GC，这里只是为了更清楚说明问题。
        System.out.println("After second GC");
        System.out.println(userSoftRef.get());

        Thread.sleep(1000);
    }

}

/*
可能的一种输出为：
Before GC
User{id=1, name='Tom'}                  （第一次从软引用获取对象）
After First GC
User{id=1, name='Tom'}                  （第一次GC后软引用对象没有回收）
user id 1 is delete(has been GC)        （引用队列探测到对象被删除）
After second GC
null                                    （对象已被回收，无法再通过软引用获取对象）
 */
